<?php
namespace store\minio;

use store\minio\MinioConfig;
use utils\CryptoTool;

/*
 文件续传对象。负责将文件块写到服务器指定目录中
 
 更新记录：
    2022-06-15 创建
 */
class MinioAuthorization
{
    private $cfg=null;
    private $timeCur="";
    private $timeIso="";
    private $method="PUT";//PUT,GET,POST
    private $uri = "";
    private $host = "";
    public $data_sha256_hash="";
    public $dataMd5="";
    public $contentLength=0;
    private $timeNow="";
    private $contentType="";
    private $m_heads=array();
    private $CanonicalQueryString="";
    
    function __construct()
    {        
        $this->cfg = new MinioConfig();
        $this->initTimeIso();
        
        //header
        $this->m_heads=array(
            "content-length"=>"",
            "content-type"=>"",
            "content-"=>"",
            "content-md5"=>"",
            "x-amz-content-sha256"=>"",
            "x-amz-date"=>""
        );
    }

    private function initTimeIso()
    {
        //2022-06-15T06:43:44+00:00Z
        $timeIso = date("c");
        $pos = strpos($timeIso,"+");
        $timeIso = substr($timeIso,0,$pos)."Z";

        $timeIso = str_replace("-","",$timeIso);
        $timeIso = str_replace(" ","",$timeIso);
        $timeIso = str_replace(":","",$timeIso);

        $pos = strpos($timeIso, "T");
        $timeCur = substr($timeIso,0,$pos);

        $this->timeIso = $timeIso;
        $this->timeCur = $timeCur;
    }
    
    //
    public function &setConfig(/*MinioConfig*/$cfg)
    {
        $this->cfg = $cfg;
        return $this;
    }

    private function getHeaders()
    {
        $head = $this->CanonicalHeaders();
        $arr = preg_split("[\n]",$head);
        $heads = array();

        foreach($arr as $a)
        {
            if (!strlen($a))
            {
                $end = strlen($a) - 1;
                if (strpos($a,":")!=false) $end = strpos($a,":");
                $heads[] = substr($a,0,$end);
            }
        }
        $head = join(";", $heads);
        return $head;
    }

    public function &setMethod(/*string*/$v){
        $this->method = $v;
        return $this;
    }

    public function &setData(/** */$v)
    {
        $this->contentLength = strlen($v);
        $this->data_sha256_hash = hash("sha256",$v);
        $this->dataMd5 = md5($v);
        $this->dataMd5 = substr($this->dataMd5,8,16);
        return $this;
    }

    public function getDataHash(){
        return $this->data_sha256_hash;
    }

    public function &setUrl($v)
    {
        $url = parse_url($v);
        $this->host = $url["host"];
        if($url["port"] != 80)
        {
            $this->host .= ":";
            $this->host .= $url["port"];
        }
        $this->setUri($url["path"]);
        $this->setQueryString($url["query"]);
        return $this;
    }

    public function &setUri($v)
    {
        $v = urlencode($v);
        $v = str_replace("+","%20",$v);
        $v = str_replace("*","%2A",$v);
        $v = str_replace("%7E","~",$v);
        $v = str_replace("%2F","/",$v);
        $this->uri = $v;
        return $this;
    }
    public function &setHost($v)
    {
        $this->host = $v;
        return $this;
    }
    public function &setContentType($v){
        $this->contentType=$v;
        return $this;
    }
    public function &setQueryString($v){
        if(!isset($v)) return $this;
        if(!empty($v)) return $this;
        $arr = preg_split("&",$v);
        $qs = array();
        foreach($arr as $a)
        {
            if(!strpos($a,"=")) $a+="=";
            $qs[] = $a;
        }
        $this->CanonicalQueryString = join("&",$qs);
        return $this;
    }
    public function &delHead($n){
        unset($this->m_heads[strtolower($n)]);
        return $this;
    }

    public function &addHead($n,$v)
    {
        $this->m_heads[$n] = $v;
        return $this;
    }

    public function getTimeIso(){
        return $this->timeIso;
    }

    private function HmacSHA256($data,$key){
        return CryptoTool::hmac_sha256_encode($data,$key);
    }

    private function getSignatureKey()
    {
        $kSecret = "AWS4" . $this->cfg->sk;
        $kDate = $this->HmacSHA256($this->timeCur, $kSecret);
        $kRegion = $this->HmacSHA256($this->cfg->region, $kDate);
        $kService = $this->HmacSHA256($this->cfg->service, $kRegion);
        $kSigning = $this->HmacSHA256("aws4_request", $kService);

        return $kSigning;
    }
    
    /**
     * 规范标头
     */
    private function CanonicalHeaders()
    {
        $arr = array(
            "content-length:" . $this->contentLength,
            //"content-md5:"+this.dataMd5,
            "content-type:" . $this->contentType,
            "host:"+ $this->host,
            "x-amz-content-sha256:" . $this->data_sha256_Hash,
            //"x-amz-content-sha256:"+this.data_sha256_Hash,
            "x-amz-date:" . $this->timeIso
        );

        //设置header值
        if ( array_keys($this->m_heads,"content-length") )
            $this->m_headers["content-length"] = $this->contentLength;
        if (array_keys($this->m_heads,"content-type"))
            $this->m_headers["content-type"] = $this->contentType;
        if (array_keys($this->m_heads,"host"))
            $this->m_headers["host"] = $this->host;
        if (array_keys($this->m_heads,"content-md5"))
            $this->m_headers["content-md5"] = $this->dataMd5;
        if (array_keys($this->m_heads,"x-amz-content-sha256"))
            $this->m_headers["x-amz-content-sha256"] = $this->data_sha256_Hash;
        if (array_keys($this->m_heads,"x-amz-date"))
            $this->m_headers["x-amz-date"] = $this->timeIso;

        $hs = array();
        foreach($this->m_heads as $k => $v)
        {
            $hs[] = $k . ":" . $v;
        }
        sort($hs);

        $str = join("\n",$hs) . "\n";        
        return $str;
    }
    
    /**
     * 凭证范围值，
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
     * 示例：
     * 	20150830/us-east-1/iam/aws4_request
     * @return
     */
    private function CredentialScope()
    {
        $arr = array(
            $this->timeCur,
            $this->cfg->region,
            "s3/aws4_request"
        );

        $str = join("/", $arr);

        return $str;
    }

    /**
        * 规范请求头签名
        * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-canonical-request.html
        * 格式：
        * CanonicalRequest =
            HTTPRequestMethod + '\n' +
            CanonicalURI + '\n' +
            CanonicalQueryString + '\n' +
            CanonicalHeaders + '\n' +
            SignedHeaders + '\n' +
            HexEncode(Hash(RequestPayload))
        * @return
        */
    /// </summary>
    /// <returns></returns>
    private function HashCanonicalRequest()
    {
        $arr = array(
            $this->method,
            $this->uri,
            $this->CanonicalQueryString,//请求字符串
            $this->CanonicalHeaders(),//请求头参数
            $this->getHeaders(),//签名头
            $this->data_sha256_Hash//内容-sha256
        );

        $str = join("\n", $arr);
        $str = hash("sha256", $str);
        return $str;
    }

    /**
     * 待签名字符串
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-create-string-to-sign.html
     * 格式：
     * StringToSign =
        Algorithm + \n +
        RequestDateTime + \n +
        CredentialScope + \n +
        HashedCanonicalRequest
        * @return
        */
    private function StringSign()
    {
        $arr = array(
            $this->cfg->algorithm,
            $this->timeIso,
            $this->CredentialScope(),
            $this->HashCanonicalRequest()
        );
        //待签名字符串
        $str = join("\n", $arr);

        //计算签名密钥
        $key = $this->getSignatureKey();
        //Debug.WriteLine("签名密钥：" + Md5Tool.base16(key));

        //签名
        $str = CryptoTool::hmac_sha256_encode($str, $key);
        //Debug.WriteLine("密钥编码：" + str);

        return $str;
    }

    /**
     * 待签名字符串
     * @return
     */
    public function Credential()
    {
        $arr = array(
            $this->cfg->ak,//
            $this->timeCur,//时间：20220519
            $this->cfg->region,//region
            $this->cfg->service,//服务
            "aws4_request"
        );
        $v = join("/", $arr);
        //System.out.println("待签名字符串："+v);
        return $v;
    }

    /**
     * Authorization 标头,添加到Header中
     * https://docs.aws.amazon.com/zh_cn/general/latest/gr/sigv4-add-signature-to-request.html
     * 格式：
     * 	Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature
     * 示例：
     * 	Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
     * @return
     */
    public function Authorization()
    {
        return $this->cfg->algorithm . " " .
                "Credential=" . $this->Credential() . ", " .
                "SignedHeaders=" . $this->getHeaders() . ", " .//签名头
                "Signature=" . $this->StringSign();
    }
}
?>